package net.bingosoft.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ResourceUtils;
/**
 * 2007-8-22 下午05:12:58
 * Author:yangdongyu(yangdy@bingosoft.net)
 * ReadMe:支持不同编码的Properties,使用时只需在属性文件中指定编码方式即可:加入一个ENCODING
 * 的属性,属性值为属性文件使用的编码（该属性默认值为UTF-8）,可以在属性文件中直接写中文,而无需转换
 * ChangeLog:
 */
public class EncodedProperties extends Properties {
	private static final Log log = LogFactory.getLog(EncodedProperties.class);
	private static final long serialVersionUID = 1L;
	public static final String ENCODING_KEY = "ENCODING";
	/** Default encoding for properties. */
	private static final String ENCODING_PROPERTIES = "ISO-8859-1";
	/** Default share-it encoding if key not present in properties. */
	public static final String ENCODING_DEFAULT = ENCODING_PROPERTIES;
	/** property file */
	private File propFile;
	/** lastModifiedTime */
	private long lastModifiedTime;
	private String path;
	private static final Object lock = new Object();
	private List<EncodedProperties> subProperties = new ArrayList<EncodedProperties>();;
	/**
	 * 总的热修改开关,当在正式生产环境中,我们将GLOBEL_ENABLE_RELOAD置为false关闭所有的自动加载功能
	 */
	public static boolean GLOBEL_ENABLE_RELOAD = true;
	/**
	 * 是否支持修改后自动重新加载
	 */
	private boolean autoReloadOnModify = false;

	public String getPath() {
		return path;
	}

	public EncodedProperties() {
		this(new Properties());
	}

	/**
	 * 属性文件重新加载
	 * @throws FileNotFoundException 
	 */
	public synchronized void refresh(File resFile) {
		if(this.path != null){
			log.debug("refresh resource " + resFile.getName());
			try {
				InputStream is = new FileInputStream(resFile);
				this.lastModifiedTime = resFile.lastModified();
				if(this.path.endsWith(".xml")){
					this.loadFromXml(is);
				}else if(this.path.endsWith(".properties")){
					this.load(is);
				}
			} catch (Exception e) {
				log.error("刷新资源文件时出现错误..." ,e);
			}
		}
		if(this.subProperties != null && !this.subProperties.isEmpty()){
			for(EncodedProperties p : this.subProperties){
				p.refresh(p.propFile);
			}
		}
	}


	@Override
	public String getProperty(String key) {
		if(GLOBEL_ENABLE_RELOAD){
			synchronized (lock) {
				this.refreshIfModified();
			}
		}
		String property = super.getProperty(key);
		if(property == null && this.subProperties != null && !this.subProperties.isEmpty()){
			for(EncodedProperties p : this.subProperties){
				if(p.containsKey(key)){
					return p.getProperty(key);
				}
			}
		}
		return property;
	}

	/**
	 * @throws NullPointerException if <tt>defaults</tt> is <tt>null</tt>.
	 */
	public EncodedProperties(Properties defaults) {
		super(defaults);
		//Make sure we have a proper default for the encoding.
		defaults.setProperty(ENCODING_KEY, ENCODING_DEFAULT);
	}

	@SuppressWarnings("unchecked")
	public void load(InputStream inStream) throws IOException {
		super.load(inStream);
		String encoding = super.getProperty(ENCODING_KEY);
		if (ENCODING_PROPERTIES.equals(encoding)) {
			return;
		}
		//Convert properties
		try {
			Map.Entry[] entries = new Map.Entry[entrySet().size()];
			entrySet().toArray(entries);
			for (int i = entries.length; --i >= 0; ) {
				Map.Entry entry = entries[i];
				String value = (String) entry.getValue();
				value = new String(value.getBytes(ENCODING_PROPERTIES),encoding);
				String key =  new String(((String)entry.getKey()).getBytes(ENCODING_PROPERTIES),encoding);
				setProperty(key, value);
			}
		}
		catch (UnsupportedEncodingException ignored) {}

	}



	/**
	 * 获取属性文件的一个实例
	 * @param propertyFileLocation 属性文件在类路径中的位置
	 * @return EncodedProperties
	 */
	protected static EncodedProperties getInstance(String propertyFileLocation) {
		//param check
		if(propertyFileLocation == null || propertyFileLocation.trim().equals("")) throw new IllegalArgumentException("属性文件的路径不能为空 ....");
		EncodedProperties p =  new EncodedProperties();
		ResourcePatternResolver rer = new PathMatchingResourcePatternResolver() ;
		Resource[] resources = null;
		try {
			resources = rer.getResources(propertyFileLocation);
		} catch (IOException ioe) {
			log.error("读取资源文件时出现错误,返回null ...",ioe);
			return null;
		}
		
		for(Resource res : resources){
			try{
				InputStream is = null;
				URL resourceUrl = res.getURL();
				String fileName = res.getFilename() ;
				if (ResourceUtils.URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {//配置文件存在于文件系统
					EncodedProperties subProperty = new EncodedProperties();
					subProperty.propFile = res.getFile();
					subProperty.path = subProperty.propFile.getAbsolutePath();
					subProperty.lastModifiedTime = subProperty.propFile.lastModified();
					is = new FileInputStream(subProperty.propFile);
					if(fileName.endsWith(".xml")){
						subProperty.loadFromXml(is) ;
					}else  if(fileName.endsWith(".properties")){
						subProperty.load(is);         
					}
					p.subProperties.add(subProperty);
					log.debug("成功从文件系统加载资源文件[" + subProperty.path + "]...");
				}else{//资源文件不存在于文件系统
					log.debug("资源文件[" + fileName + "]不存在于文件系统,将通过ClassLoader进行加载,热修改功能关闭...");
					is = res.getInputStream() ;
					if(fileName.endsWith(".xml")){
						p.loadFromXml(is) ;
					}else  if(fileName.endsWith(".properties")){
						p.load(is);         
					}
				}
				
			}catch(Exception e){
				log.error("加载属性文件时出现错误 ... ",e) ;
			}
		}
		return p;
	}


	/**
	 * 通过输入流构建EncodedProperties
	 * @param p EncodedProperties
	 * @param is InputStream
	 * @throws Exception
	 */
	protected synchronized void loadFromXml(InputStream is) throws Exception{
		try {
			this.loadFromXML(is);
		} catch (RuntimeException e) {
			SAXBuilder builder = new SAXBuilder(); 
			Document doc = builder.build(is) ;
			Element rootElement = doc.getRootElement();
			List<?> list = rootElement.getChildren("entry") ;
			if(list == null || list.isEmpty()) return ;
			for(int i=0 ;i<list.size() ;i++){
				Element element = (Element)list.get(i) ;
				String key = element.getAttributeValue("key") ;
				String value = element.getText() ;
				if(!ENCODING_PROPERTIES.equals(super.getProperty(ENCODING_KEY))){
					// 处理编码
					value = new String(value.getBytes(ENCODING_PROPERTIES),super.getProperty(ENCODING_KEY));
				}
				super.setProperty(key, value) ;
			}
		}
	}

	/**
	 * 获取该类的一个实例
	 * @param propertyFileLocation 属性文件在类路径中的位置
	 * @param autoReloadOnModify 该属性文件是否支持热修改
	 * @return
	 */
	public static Properties getInstance(String propertyFileLocation,boolean autoReloadOnModify){
		EncodedProperties p = getInstance(propertyFileLocation);
		p.setAutoReloadOnModify(autoReloadOnModify);
		return p;
	}
	/**
	 * 检查属性文件是否被修改过
	 * @return
	 */
	protected boolean refreshIfModified(){
		boolean isModified = false;
		if(this.propFile != null){
			isModified = this.lastModifiedTime < this.propFile.lastModified();
			if(isModified){
				this.refresh(this.propFile);
			}
		}
		if(this.subProperties != null && !this.subProperties.isEmpty()){
			for(EncodedProperties p : this.subProperties){
				if(p.refreshIfModified()){
					isModified = true;
				}
			}
		}
		return isModified;
	}

	public void setAutoReloadOnModify(boolean autoReloadOnModify) {
		this.autoReloadOnModify = autoReloadOnModify;
	}

	public boolean isAutoReloadOnModify() {
		return autoReloadOnModify;
	}

	public List<EncodedProperties> getSubProperties() {
		return subProperties;
	}


}
